package com.bingxue.edu.management.auth.service;

import cn.dev33.satoken.stp.StpInterface;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.json.JSONUtil;
import com.bingxue.edu.common.config.SecurityPolicyProperties;
import com.bingxue.edu.common.enums.CommonStatusEnum;
import com.bingxue.edu.common.enums.UserType;
import com.bingxue.edu.framework.common.constant.CacheConstants;
import com.bingxue.edu.framework.common.util.HttpContextHolder;
import com.bingxue.edu.framework.common.util.ValidationUtils;
import com.bingxue.edu.framework.redis.RedisUtils;
import com.bingxue.edu.management.auth.EmployeeAuthUtil;
import com.bingxue.edu.management.auth.model.AuthEmployee;
import com.bingxue.edu.management.auth.model.EmployeeInfo;
import com.bingxue.edu.management.system.model.entity.Employee;
import com.bingxue.edu.management.system.model.entity.Org;
import com.bingxue.edu.management.system.model.entity.SocialAccount;
import com.bingxue.edu.management.system.service.EmployeeService;
import com.bingxue.edu.management.system.service.OrgService;
import com.bingxue.edu.management.system.service.RoleEmployeeService;
import com.bingxue.edu.management.system.service.RoleResourceService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import me.zhyd.oauth.model.AuthUser;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;

import static com.bingxue.edu.management.system.model.entity.table.EmployeeTableDef.EMPLOYEE;

/**
 * 员工登录业务实现
 *
 * @author panbo
 * @since 2024/6/28
 */
@Service
@RequiredArgsConstructor
public class EmployeeAuthService implements StpInterface {

    private final EmployeeService employeeService;
    private final RoleEmployeeService roleEmployeeService;
    private final RoleResourceService roleResourceService;
    private final OrgService orgService;
    private final SocialAccountService socialAccountService;
    private final SecurityPolicyProperties securityPolicyProperties;

    /**
     * 账号登录
     *
     * @param username 用户名
     * @param password 密码
     * @param request  请求对象
     * @return 令牌
     */
    public String loginByAccount(String username, String password, HttpServletRequest request) {
        Employee employee = employeeService.getByUsername(username);
        boolean isError = ObjectUtil.isNull(employee) || !DigestUtil.bcryptCheck(password, employee.getPassword());
        this.checkUserLocked(username, request, isError);
        ValidationUtils.throwIf(isError, "用户名或密码错误");
        this.checkUserStatus(employee);
        return this.login(employee);
    }

    /**
     * 手机号登录
     *
     * @param phone 手机号
     * @return 令牌
     */
    public String loginByPhone(String phone) {
        Employee employee = employeeService.getByPhone(phone);
        ValidationUtils.throwIfNull(employee, "此手机号未绑定本系统账号");
        this.checkUserStatus(employee);
        return this.login(employee);
    }

    /**
     * 三方账号登录
     *
     * @param authUser 三方账号信息
     * @return 令牌
     */
    @Transactional(rollbackFor = Exception.class)
    public String loginBySocial(AuthUser authUser) {
        String source = authUser.getSource();
        String openId = authUser.getUuid();
        SocialAccount userSocial = socialAccountService.getBySourceAndOpenId(UserType.EMPLOYEE, source, openId);
        ValidationUtils.throwIfNull(userSocial, "此社交账号号未绑定本系统账号");
        Employee employee = employeeService.getById(userSocial.getUserId());
        ValidationUtils.throwIfNull(employee, "此手机号未绑定本系统账号");
        this.checkUserStatus(employee);
        userSocial.setMetadata(JSONUtil.toJsonStr(authUser));
        userSocial.setLastLoginTime(LocalDateTime.now());
        socialAccountService.saveOrUpdate(userSocial);
        return this.login(employee);
    }

    /**
     * 登录并缓存用户信息
     *
     * @param employee 用户信息
     * @return 令牌
     */
    private String login(Employee employee) {
//        employee.setLastLoginAddress(IpUtils.getIpv4Address(employee.getLastLoginIp()));
        // 更新登录信息
        AuthEmployee authEmployee = buildAuthEmployee(employee);
        return EmployeeAuthUtil.login(authEmployee);
    }

    public AuthEmployee buildAuthEmployee(Employee employee) {
        employeeService.updateChain()
                .set(EMPLOYEE.LAST_LOGIN_TIME, LocalDateTime.now())
                .set(EMPLOYEE.LAST_LOGIN_IP, JakartaServletUtil.getClientIP(HttpContextHolder.getRequest()))
                .where(EMPLOYEE.ID.eq(employee.getId()))
                .update();
        // 装配用户信息
        EmployeeInfo employeeInfo = BeanUtil.copyProperties(employee, EmployeeInfo.class);
        // 部门名称
        if (StrUtil.isNotEmpty(employee.getOrgId())) {
            orgService.getByIdOpt(employee.getOrgId()).ifPresent(item -> employeeInfo.setOrgName(item.getName()));
        }
        // 角色列表
        List<String> roleIdList = roleEmployeeService.getRoleIdListByEmployeeId(employee.getId());
        // 资源列表
        List<String> resourceCodeList = roleResourceService.getResourceCodeListByRoleIds(roleIdList);

        // 装配认证信息
        AuthEmployee authEmployee = new AuthEmployee();
        authEmployee.setUserInfo(employeeInfo);
        authEmployee.setRoleIdList(roleIdList);
        authEmployee.setResourceCodeList(resourceCodeList);

        return authEmployee;
    }

    /**
     * 检查用户状态
     *
     * @param employee 用户信息
     */
    private void checkUserStatus(Employee employee) {
        ValidationUtils.throwIfEqual(CommonStatusEnum.DISABLED, employee.getStatus(), "此账号已被禁用，如有疑问，请联系管理员");
        Org org = orgService.getById(employee.getOrgId());
        if (org != null) {
            ValidationUtils.throwIfEqual(CommonStatusEnum.DISABLED, org.getStatus(), "此账号所属部门已被禁用，如有疑问，请联系管理员");
        }
    }

    /**
     * 检测用户是否已被锁定
     *
     * @param username 用户名
     * @param request  请求对象
     * @param isError  是否登录错误
     */
    private void checkUserLocked(String username, HttpServletRequest request, boolean isError) {
        int maxErrorCount = securityPolicyProperties.getMaxLoginFailedCount();
        // 小于等于0不锁定
        if (maxErrorCount <= 0) {
            return;
        }
        // 检测是否已被锁定
        String key = CacheConstants.USER_PASSWORD_ERROR_KEY_PREFIX + RedisUtils.formatKey(username, JakartaServletUtil.getClientIP(request));
        int lockMinutes = securityPolicyProperties.getLoginFailedLockMinutes();
        int currentErrorCount = ObjectUtil.defaultIfNull(RedisUtils.get(key), 0);
        ValidationUtils.throwIf(currentErrorCount >= maxErrorCount, "账号锁定{}分钟，请稍后再试", lockMinutes);
        // 登录成功清除计数
        if (!isError) {
            RedisUtils.delete(key);
            return;
        }
        // 登录失败递增计数
        currentErrorCount++;
        RedisUtils.set(key, currentErrorCount, Duration.ofMinutes(lockMinutes));
        ValidationUtils.throwIf(currentErrorCount >= maxErrorCount, "登录失败已达{}次，账号锁定{}分钟", maxErrorCount, lockMinutes);
    }

    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        return EmployeeAuthUtil.getAuthUser().getResourceCodeList();
    }

    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        return EmployeeAuthUtil.getAuthUser().getRoleIdList();
    }
}
